package com.cordys.opensource.crom.sql;

import java.util.ArrayList;
import java.util.List;


public class Where
{
	public enum Operator
	{
		Equals(" = "),
		NotEqual(" <> "),
		Greater(" > "),
		Less(" < "),
		GreaterEqual(" >= "),
		LessEqual(" <= "),
		Like(" like "),
		Between(" between "),
		In(" in ");
		private final String operator;
		Operator(String operator)
		{
			this.operator = operator;
		}
		public String toString()
		{
			return this.operator;
		}
	}
	
	private final String leftPrefix;
	private final Column left;
	private final String leftPostfix;
	private final Operator operator;
	private final String rightPrefix;
	private final Object right;
	private final String rightPostfix;
	private final List<Object> whereChain = new ArrayList<Object>();
	private final Query query;
	
	Where(Query query, Query nestedExistsQuery)
	{
		this.query = query;
		this.leftPrefix = "";
		this.left = null;
		this.leftPostfix = "";
		this.operator = null;
		this.rightPrefix = "";
		this.right = nestedExistsQuery;
		this.rightPostfix = "";
		query.addNestedQuery(nestedExistsQuery);
	}
	
	Where(Query query, String expression)
	{
		this.query = query;
		this.leftPrefix = "";
		this.left = null;
		this.leftPostfix = "";
		this.operator = null;
		this.rightPrefix = "";
		this.right = expression;
		this.rightPostfix = "";
	}
	
	private boolean isNativeDatabaseType(Class<?> clazz)
	{
		if (   Number.class.isAssignableFrom(clazz)
			|| CharSequence.class.isAssignableFrom(clazz)
			|| java.util.Date.class.isAssignableFrom(clazz)
			|| Boolean.class.isAssignableFrom(clazz)
			|| clazz.equals(int.class)
			|| clazz.equals(boolean.class)
			|| clazz.equals(double.class)
			|| clazz.equals(float.class)
			|| clazz.equals(short.class)
			|| clazz.equals(long.class)
			|| clazz.equals(char.class)
			|| clazz.equals(byte.class)
		   )
		{
			return true;
		}
		return false;
	}
	
	Where( Query query
		 , String columnPrefix
		 , Column column
		 , String columnPostfix
		 , Operator operator
		 , String operandPrefix
		 , Object operand
		 , String operandPostfix )
	{
		this.query = query;
		this.leftPrefix = columnPrefix;
		this.left = column;
		this.leftPostfix = columnPostfix;
		Class<?> columnType = column.getAttributeInfo().getAttributeClass();
		this.operator = operator;
		this.rightPrefix = operandPrefix;
		if (operand==null)
		{
			this.right = null;
		}
		else if (columnType.isAssignableFrom(operand.getClass()))
		{
			this.right = new XQYParameter(column, operand);
		}
		else if (isNativeDatabaseType(operand.getClass()))
		{
			// This condition is perhaps a bit too 'relaxed'?
			// It has been added for following use case:
			//  a Date operand is passed, where database field is a String
			//  In this case, we still convert to XQYParameter. 
			// Note on 'too relaxed': It is not; all primitive types are parameterized. 
			//  Checking of type is not done; we could add logic for that, 
			//  but this will also result in DB query errors, which is a better way to check.
			this.right = new XQYParameter(column, operand);
		}
		else
		{
			this.right = operand;
		}
		// Register nested sub queries if any
		if (operand!=null && operand instanceof Query)
		{
			column.getQuery().addNestedQuery((Query)operand);
		}
		this.rightPostfix = operandPostfix;
	}

	public Where and(Column column, Operator operator, Object operand)
	{
		Where where = new Where(query, "", column, "", operator,"", operand, "");
		whereChain.add(" and ");
		whereChain.add(where);
		return where;
	}

	public Where and(String columnPrefix
	        , Column column
	        , String columnPostfix
	        , Operator operator
	        , String operandPrefix
	        , Object operand
	        , String operandPostfix)
	{
        Where where = new Where(query, columnPrefix, column, columnPostfix,  operator, operandPrefix, operand, operandPostfix);
        whereChain.add(" and ");
        whereChain.add(where);
        return where;
	}

    public Where or(String columnPrefix
            , Column column
            , String columnPostfix
            , Operator operator
            , String operandPrefix
            , Object operand
            , String operandPostfix)
    {
        Where where = new Where(query, columnPrefix, column, columnPostfix,  operator, operandPrefix, operand, operandPostfix);
        whereChain.add(" or ");
        whereChain.add(where);
        return where;
    }

    public Where or(Column column, Operator operator, Object operand)
	{
		Where where = new Where(query, "", column, "",  operator, "", operand, "");
		whereChain.add(" or ");
		whereChain.add(where);
		return where;
	}
	
	public String toString()
	{
		StringBuffer expression = new StringBuffer("");
		if (!whereChain.isEmpty())
		{
			expression.append(" (");
		}
		if (left==null && right instanceof String)
		{
			expression.append( rightPrefix + right + rightPostfix );
		}
		else if (left==null) // Then it is an EXISTS where
		{
			expression.append(" EXISTS (" + rightPrefix + right + rightPostfix + ")");
		}
		else if (right==null)
		{
			// Make proper NULL query
			if (operator==Operator.Equals)
			{
				expression.append("" + leftPrefix + left + leftPostfix + " IS NULL");
			}
			else if (operator==Operator.NotEqual)
			{
				expression.append("" + leftPrefix + left + leftPostfix + " IS NOT NULL");
			}
			else
			{
				expression.append("" + leftPrefix + left + leftPostfix + operator + " NULL ");
			}
		}
		else if (right instanceof Query)
		{
			expression.append("" + leftPrefix + left + leftPostfix + operator + "(" + rightPrefix + right + rightPostfix + ")");
		}
		else
		{
			expression.append("" + leftPrefix + left + leftPostfix + operator + rightPrefix + right + rightPostfix);
		}
		for (Object chainElement : whereChain)
		{
			expression.append(chainElement.toString());
		}
		if (!whereChain.isEmpty())
		{
			expression.append(")");
		}
		return expression.toString();
	}
}
